package com.globallogic.week1;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

/**
 * Linear and singly linked list.
 */
public class SinglyLinkedList<E> {

	/**
	 * Structure is used for implementation of linked list.
	 */
	static class ListEntity {
		private ListEntity next;
		private Object element;

		public ListEntity(ListEntity next, Object element) {
			this.next = next;
			this.element = element;
		}

		public ListEntity getNext() {
			return next;
		}

		public void setNext(ListEntity next) {
			this.next = next;
		}

		public Object getElement() {
			return element;
		}
	}

	/**
	 * Holds reference to first element of linked list.
	 */
	ListEntity head;
	private static Logger log = Logger.getLogger(SinglyLinkedList.class);
	
	public SinglyLinkedList() {
		BasicConfigurator.configure();
	}

	/**
	 * Returns <tt>true</tt> if this list contains the specified element.
	 * 
	 * @param o
	 *            element whose presence in this list is to be tested.
	 * @return <tt>true</tt> if this list contains the specified element.
	 */
	public boolean contains(Object o) {
		ListEntity element  = head;
		log.info("Contains: beginning to find matches.");
		
		while( (element != null) ){
			
			if (element.element.equals(o)){
				log.info(String.format("Contains: list contains specified element(%s).",
						o.toString()));
				return true;
			}
			
			element = element.getNext();
		}
		
		log.info(String.format("Contains: no such element (%s) in list.",
				o.toString()));
		return false;
	}

	/**
	 * Returns the number of elements in this list.
	 * 
	 * @return the number of elements in this list.
	 */
	public int size() {
		int size = 0;
		
		log.info("Contains: beginning to find list size");
		if (head == null) {
			return size;
		} else {
			ListEntity element = head;
			
			while (element != null) {
				size++;
				element = element.getNext();
			}
		}
		log.info(String.format("Size: size was found successfully. Size is: %d",
				size));
		return size;
	}

	/**
	 * Appends the specified element to the end of this list.
	 * 
	 * @param o
	 *            element to be appended to this list.
	 */
	public void add(E o) {
		log.info("Add: beginning to adding element to list");
		
		if (head == null) {
			head = new ListEntity(null, o);
			log.info(String.format("Add: head (%s) added successfully to list", 
					o.toString()));
		} else {
			ListEntity element = head;
			
			while (element.getNext() != null) {
				element = element.getNext();
			}
			
			log.info(String.format("Add: element(%s) added successfully to list", 
					o.toString()));
			element.setNext(new ListEntity(null, o));
		}
	}

	/**
	 * Removes the first occurrence of the specified element in this list.
	 * 
	 * @param o
	 *            element to be removed from this list, if present.
	 * @return <tt>true</tt> if the list contained the specified element.
	 */
	public boolean remove(E o) {
		ListEntity element = head;
		boolean result = false;
		log.info("Remove: beginning to removing element from list.");
		
		if (!contains(o)){
			log.warn("Remove: no such element is list.");
			return false;
		}
		
		while (element != null){
			
			if (head.element.equals(o) ){
				ListEntity temp = head.getNext();
				log.info(String.format("Remove: element(%s) deleted from the list.",
						head.element.toString()));
				head = null;
				head = temp;
				temp = null;
				result = true;
			}
			else if (element.getNext() == null){
//				element.setNext(null);
				result = true;
			} 
			else if (element.getNext().element.equals(o) ){
				ListEntity temp = element.getNext();
				
				if (temp.getNext() != null){
					element.setNext(temp.getNext());
					log.info(String.format("Remove: element(%s) deleted from the list.",
							temp.element.toString()));
					temp = null;
					result = true;
					continue;
				} else {
					element.setNext(null);
					temp = null;
					log.info("Remove: element deleted from the list.");
					result = true;
				} 
			} 
			
			element = element.getNext();	
		}
		log.info("Removing is completed");
		return result;
	}

	/**
	 * Removes all of the elements from this list.
	 */
	public void clear() {
		log.info("Clear: beginning of list clearing.");
		if (head == null){
			return;
		} else {
			ListEntity element = head;
			while (head != null){
				ListEntity temp = head.getNext();
				log.info(String.format("Clear: element(%s) deleted from the list.",
						head.element.toString()));
				head = null;
				head = temp;
			}
		}
		log.info("Clearing is completed.");
	}

	/**
	 * Returns the element at the specified position in this list.
	 * 
	 * @param index
	 *            index of element to return.
	 * @return the element at the specified position in this list.
	 * 
	 * @throws IndexOutOfBoundsException
	 *             if the specified index is out of range
	 */
	public E get(int index) {
		log.info("Get: starting");
		if (index >= size() || index < 0){
			log.error("Get: index out of range.");
			throw new IndexOutOfBoundsException();
		}
		ListEntity element = head;
		
		for(int i = 0; i < index + 1; i++){
			
			if (i == index){
				log.info(String.format("Get: finished, returned element: %s",
						element.element.toString()));
				return (E) element.getElement();
			} 
			element = element.getNext();
		}
		
		log.info(String.format("Get: finished, returned element: %s",
				element.element.toString()));
		
		return null;
	}
}